from typing import Iterable
from typing import NamedTuple
from typing import Optional
from typing import Union

from trashcli.lib.path_of_backup_copy import path_of_backup_copy
from trashcli.parse_trashinfo.parse_deletion_date import parse_deletion_date
from trashcli.parse_trashinfo.parse_original_location import parse_original_location
from trashcli.restore.file_system import FileReader
from trashcli.restore.info_dir_searcher import InfoDirSearcher
from trashcli.restore.restore_logger import RestoreLogger
from trashcli.restore.trashed_file import TrashedFile


class TrashedFiles:
    def __init__(
        self,
        logger,  # type: RestoreLogger
        file_reader,  # type: FileReader
        searcher,  # type: InfoDirSearcher
    ):
        self.logger = logger
        self.file_reader = file_reader
        self.searcher = searcher

    def all_trashed_files(
        self,
        trash_dir_from_cli,  # type: Optional[str]
    ):  # type: (...) -> Iterable[TrashedFile]
        for event in self.all_trashed_files_internal(trash_dir_from_cli):
            if type(event) is NonTrashinfoFileFound:
                self.logger.warning("Non .trashinfo file in info dir")
            elif type(event) is NonParsableTrashInfo:
                self.logger.warning(
                    "Non parsable trashinfo file: %s, because %s"
                    % (event.path, event.reason)
                )
            elif type(event) is IOErrorReadingTrashInfo:
                self.logger.warning(str(event))
            elif type(event) is TrashedFileFound:
                yield event.trashed_file
            else:
                raise RuntimeError()

    def all_trashed_files_internal(
        self,
        trash_dir_from_cli,  # type: Optional[str]
    ):  # type: (...) -> Iterable[Event]
        for info_file in self.searcher.all_file_in_info_dir(trash_dir_from_cli):
            if info_file.type == "non_trashinfo":
                yield NonTrashinfoFileFound(info_file.path)
            elif info_file.type == "trashinfo":
                try:
                    contents = self.file_reader.contents_of(info_file.path)
                    original_location = parse_original_location(
                        contents, info_file.volume
                    )
                    deletion_date = parse_deletion_date(contents)
                    backup_file_path = path_of_backup_copy(info_file.path)
                    trashedfile = TrashedFile(
                        original_location,
                        deletion_date,
                        info_file.path,
                        backup_file_path,
                    )
                    yield TrashedFileFound(trashedfile)
                except ValueError as e:
                    yield NonParsableTrashInfo(info_file.path, e)
                except IOError as e:
                    yield IOErrorReadingTrashInfo(info_file.path, str(e))
            else:
                raise RuntimeError(
                    "Unexpected file type: %s: %s", info_file.type, info_file.path
                )


class NonTrashinfoFileFound(
    NamedTuple(
        "NonTrashinfoFileFound",
        [
            ("path", str),
        ],
    )
):
    pass


class TrashedFileFound(
    NamedTuple(
        "TrashedFileFound",
        [
            ("trashed_file", TrashedFile),
        ],
    )
):
    pass


class NonParsableTrashInfo(
    NamedTuple(
        "NonParsableTrashInfo",
        [
            ("path", str),
            ("reason", Exception),
        ],
    )
):
    pass


class IOErrorReadingTrashInfo(
    NamedTuple(
        "IOErrorReadingTrashInfo",
        [
            ("path", str),
            ("error", str),
        ],
    )
):
    pass


Event = Union[
    NonTrashinfoFileFound,
    TrashedFileFound,
    NonParsableTrashInfo,
    IOErrorReadingTrashInfo,
]
